package com.googlecode.imanager.client;

import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.Set;

import com.googlecode.imanager.client.bridge.BridgeManager;
import com.googlecode.imanager.client.bridge.BridgeType;
import com.googlecode.imanager.client.bridge.IApplication;
import com.googlecode.imanager.client.bridge.style.IParentThemeHandler;
import com.googlecode.imanager.client.bridge.style.ITheme;
import com.googlecode.imanager.client.event.PhaseEvent;
import com.googlecode.imanager.client.io.HttpRequest;
import com.googlecode.imanager.client.io.Path;
import com.googlecode.imanager.client.style.Theme;
import com.googlecode.imanager.client.style.listener.StyleAttribute;
import com.googlecode.imanager.client.style.listener.StyleWrapper;
import com.googlecode.imanager.client.style.listener.ThemeListener;
import com.googlecode.imanager.client.util.Dimension;
import com.googlecode.imanager.client.util.HasTheme;
import com.googlecode.imanager.client.util.Logger;
import com.googlecode.imanager.client.util.UniqueList;

/**
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public class Application implements IApplication, HasTheme, Serializable, Cloneable {
    
    private static final long serialVersionUID = 1L;
    
    private Theme theme;
    private Path basePath;
    private ChildList<Document> documents;
    
    Focusable focusControl;
    transient IApplication bridge;

    public Application() {
        super();
        documents = new ChildList<Document>();
    }
    
    protected void onDocumentAdded(Document document){
    }
    
    protected void onDocumentRemoved(Document document){
    }
    
    protected void onPhaseEvent(PhaseEvent e) {
        e.getTarget().onPhaseEvent(e);
    }
    
    public final void add(Document document){
        documents.add(document);
    }
    
    public final void remove(Document document){
        documents.remove(document);
    }
    
    public final Set<Document> getDocuments(){
        return documents;
    }
    
    public void setBasePath(Path basePath) {
        this.basePath = basePath;
    }

    @Override
    public Path getBasePath() {
        if (basePath == null){
            return getBridge().getBasePath();
        }
        return basePath;
    }

    public Theme getTheme() {
        return theme;
    }

    public void setTheme(Theme theme) {
        if (this.theme != theme){
            Theme oldTheme = this.theme;
            
            if (oldTheme != null){
                this.theme = null;
                oldTheme.removeListener(documents);
            }
            
            if (theme != null){
                this.theme = theme;
                theme.addListener(documents);
            }
        }
    }

    public Focusable getFocusControl() {
        return focusControl;
    }

    public void setFocusControl(Focusable focusControl) {
        if (focusControl != null
                && !(focusControl instanceof Control)){
            throw new IllegalArgumentException("focusControl is not a Control descendant");
        }
        
        if (this.focusControl != focusControl){
            BridgeManager.getInstance().setFocusControl(this, focusControl);
        }
    }

    @Override
    public final Dimension getScreenSize() {
        return getBridge().getScreenSize();
    }

    @Override
    public void handleError(Throwable e) {
        getBridge().handleError(e);
    }

    @Override
    public void httpCall(HttpRequest request) {
        getBridge().httpCall(request);
    }

    @Override
    public void showErrorDialog(String title, String message, Throwable e) {
        getBridge().showErrorDialog(title, message, e);
    }

    @Override
    public String showInputDialog(String title, String message) {
        return getBridge().showInputDialog(title, message);
    }

    @Override
    public void showMessageDialog(String title, String message) {
        getBridge().showMessageDialog(title, message);
    }

    @Override
    public boolean showQuestionDialog(String title, String message) {
        return getBridge().showQuestionDialog(title, message);
    }

    @Override
    public void showWarningDialog(String title, String message) {
        getBridge().showWarningDialog(title, message);
    }
    
    public final ITheme getDefaultTheme(){
        return BridgeManager.getInstance().getDefaultTheme(getImplementationType());
    }

    @Override
    public final boolean equals(Object obj) {
        return obj == this;
    }

    @Override
    public final int hashCode() {
        return super.hashCode();
    }

    protected final IApplication getBridge() {
        if (bridge == null){
            bridge = (IApplication) BridgeManager.getInstance().lookupImplementation(this);
        }
        return bridge;
    }
    
    @Override
    public BridgeType getImplementationType() {
        return bridge == null ? BridgeType.EMPTY : bridge.getImplementationType();
    }

    protected final Logger getLogger(){
        return BridgeManager.getInstance().getLogger(getClass());
    }
    
    protected final Logger getLogger(Class<?> ownerClass){
        return BridgeManager.getInstance().getLogger(ownerClass);
    }
        
    @Override
    public Application clone() {
        try {
            Application c = (Application) super.clone();
            
            c.theme = null;
            c.bridge = null;
            c.documents = c.new ChildList<Document>();
            
            return c;
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }

    public Application cloneTree() {
        Application c = clone();
        
        for (Document doc : getDocuments()){
            c.add(doc.cloneTree());
        }

        return c;
    }
    
    private Object readResolve() throws ObjectStreamException {
        // FIXME to test this code
        Theme theme = this.theme;
        this.theme = null;
        setTheme(theme);
        return this;
    }


    /**
     * @author wellsantos@wellsantos.com
     * @since 0.01
     */
    private class ChildList<T extends Document> extends UniqueList<T> implements ThemeListener {

        private static final long serialVersionUID = 1L;

        public ChildList() {
            super();
        }

        @Override
        protected void onAfterAdd(int index, T item) {
            item.setApplication(Application.this);
        }

        @Override
        protected void onAfterRemove(int index, T item) {
            item.setApplication(null);
        }

        @Override
        protected void onAfterReplace(int index, T oldItem, T newItem) {
            onAfterAdd(index, newItem);
        }

        @Override
        protected void onBeforeReplace(int index, T oldItem, T newItem) {
            onBeforeRemove(index, oldItem);
            onBeforeAdd(index, newItem);
            onAfterRemove(index, oldItem);
        }
        
        final IParentThemeHandler getThemeHandler() {
            return (IParentThemeHandler) getBridge();
        }
        
        @Override
        public void onThemeAttach(Theme theme) {
            getThemeHandler().onThemeAttach(theme);
            if (getThemeHandler().isFireStyleChangesToChildren()){
                StyleWrapper styleWrapper = theme.lookupWrapper(Control.class.getName(), 0);
                fireChangesToChildren(styleWrapper, StyleAttribute.ALL, styleWrapper.getStyle());
            }
        }

        @Override
        public void onThemeDetach(Theme theme) {
            setTheme(null);
            getThemeHandler().onThemeDetach(theme);
            if (getThemeHandler().isFireStyleChangesToChildren()){
                StyleWrapper styleWrapper = theme.lookupWrapper(Control.class.getName(), 0);
                fireChangesToChildren(styleWrapper, StyleAttribute.ALL, styleWrapper.getStyle());
            }
        }

        @Override
        public <E> void onStyleAttributeChanged(StyleWrapper styleWrapper, StyleAttribute<E> attribute, E attributeValue) {
            getThemeHandler().onStyleAttributeChanged(styleWrapper, attribute, attributeValue);
            if (getThemeHandler().isFireStyleChangesToChildren()){
                fireChangesToChildren(styleWrapper, attribute, attributeValue);
            }
        }
        
        protected <E> void fireChangesToChildren(StyleWrapper styleWrapper, StyleAttribute<E> attribute, E attributeValue){
            for (Document doc : documents){
                doc.getThemeListener().onStyleAttributeChanged(styleWrapper, attribute, attributeValue);
            }
        }
        
    }
    
}